/*
 * 测试 open ndc 是左手坐标系还是右手坐标系，发现果然 opengl ndc 是基于左手坐标系。 
 * 注意测试时要启用 depth-test 且每一帧要清除 z-buffer 。
 * 注意 z 轴坐标为 0.0f 被绘制出了只是部分被挡住，而 z 轴坐标为 1.0f 未被绘制出。
 */
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdio.h>

const GLchar *vertexcode = 
"#version 330 core \n"
"layout(location = 0) in vec3 pos_modelspace; \n"
"void main() { \n"
"	gl_Position = vec4(pos_modelspace, 1); \n"
"}";

const GLchar *fragmentcode = 
"#version 330 core \n"
"uniform vec3 bg; \n"
"out vec3 color; \n"
"void main() { \n"
"	color = bg; \n"
"}";

static GLuint 
load_shader(GLenum shadertype, const GLchar *shadercode) {
	GLuint shader = glCreateShader(shadertype);
	if (shader == 0) {
		printf("fail to create shader\n");
		return 0;
	}

	// 编译
	glShaderSource(shader, 1, &shadercode, NULL);
	glCompileShader(shader);

	// 检查编译结果
	GLint value = 0;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &value);
	if (value == GL_FALSE) {
		value = 0;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &value);
		if (value > 1) {
			GLchar info[value + 1];
			glGetShaderInfoLog(shader, value, NULL, info);
			printf("fail to compile shader: %s\n", info);
		}
		glDeleteShader(shader);
		shader = 0;
	}

	return shader;
}

static GLuint 
link_program(const GLchar *vertexcode, const GLchar *fragmentcode) {
	GLuint vertexshader = 0, fragmentshader = 0, program = 0;
	vertexshader = load_shader(GL_VERTEX_SHADER, vertexcode);
	if (vertexshader == 0) {
		printf("fail to create vertex shader");
		goto EXIT;
	}
	fragmentshader  = load_shader(GL_FRAGMENT_SHADER, fragmentcode);
	if (fragmentshader == 0) {
		printf("fail to create fragment shader");
		goto EXIT;
	}

	program = glCreateProgram();
	if (program == 0) {
		printf("fail to create program");
		goto EXIT;
	}

	// 链接
	glAttachShader(program, vertexshader);
	glAttachShader(program, fragmentshader);
	glLinkProgram(program);

	// 查看链接结果
	GLint value = 0;
	glGetProgramiv(program, GL_LINK_STATUS, &value);
	if (value == GL_FALSE) {
		value = 0;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &value);
		if (value > 1) {
			GLchar info[value];
			glGetProgramInfoLog(program, value, NULL, info);
			printf("fail to link program: %s\n", info);
		}
		goto EXIT;
	}

	printf("link program successfully, delete no use shader %d %d\n", vertexshader, fragmentshader);
	glDetachShader(program, vertexshader);
	glDetachShader(program, fragmentshader);
	glDeleteShader(vertexshader);
	glDeleteShader(fragmentshader);
	return program;
EXIT:
	if (vertexshader != 0)
		glDeleteShader(vertexshader);
	if (fragmentshader != 0)
		glDeleteShader(fragmentshader);
	if (program != 0)
		glDeleteProgram(program);
	return 0;
}

int 
main() {
	if (!glfwInit()) 
		return 0;

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	
	GLFWwindow *window = glfwCreateWindow(800, 600, "validate ndc use left-hand", NULL, NULL);
	if (window == NULL) {
		printf("fail to create window\n");
		glfwTerminate();
		return 0;
	}
	glfwMakeContextCurrent(window);

	glewExperimental = GL_TRUE;
	if (glewInit() != GLEW_OK) {
		glfwTerminate();
		return 0;
	}

	GLuint program = link_program(vertexcode, fragmentcode);
	if (program == 0)
		return 0;
	GLuint bglocation = glGetUniformLocation(program, "bg");

	glEnable(GL_DEPTH_TEST);

	GLuint vao = 0;
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);
		glEnableVertexAttribArray(0);
		
		const GLfloat vertices_leftup[] = {
			-1.0f, -0.5f, -1.0f,
			 0.0f, -0.5f, -1.0f,
			-0.5f,  1.0f, -1.0f,
		};
		GLuint leftup_bo = 0;
		glGenBuffers(1, &leftup_bo);
		glBindBuffer(GL_ARRAY_BUFFER, leftup_bo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_leftup), vertices_leftup, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		
		const GLfloat vertices_leftdown[] = {
			-1.0f, -1.0f, 0.5f,
			 0.0f, -1.0f, 0.5f,
			-0.5f,  0.5f, 0.5f,
		};
		GLuint leftdown_bo = 0;
		glGenBuffers(1, &leftdown_bo);
		glBindBuffer(GL_ARRAY_BUFFER, leftdown_bo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_leftdown), vertices_leftdown, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		const GLfloat vertices_rightup[] = {
			0.0f, -0.5f, 0.5f,
			1.0f, -0.5f, 0.5f,
			0.5f,  1.0f, 0.5f,
		};
		GLuint rightup_bo = 0;
		glGenBuffers(1, &rightup_bo);
		glBindBuffer(GL_ARRAY_BUFFER, rightup_bo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_rightup), vertices_rightup, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		const GLfloat vertices_rightdown[] = {
			0.0f, -1.0f, -1.0f,
			1.0f, -1.0f, -1.0f,
			0.5f,  0.5f, -1.0f,
		};
		GLuint rightdown_bo = 0;
		glGenBuffers(1, &rightdown_bo);
		glBindBuffer(GL_ARRAY_BUFFER, rightdown_bo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_rightdown), vertices_rightdown, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		const GLfloat vertices_rect[] = {
			-1.0f, -0.5f, 0.0f,
			 1.0f, -0.5f, 0.0f,
			 1.0f,  0.5f, 0.0f,
			-1.0f,  0.5f, 0.0f,
		};
		GLuint rect_bo = 0;
		glGenBuffers(1, &rect_bo);
		glBindBuffer(GL_ARRAY_BUFFER, rect_bo);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_rect), vertices_rect, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
	
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

	/**
	 * 不启用 depth test 时，之后的绘制会覆盖之前的绘制。启用后才可以看见想要的效果。
	 * 这里有个问题，为什么 z 轴坐标为 1.0f 未被绘制出，而 z 轴坐标为 0.0f 就被绘制出了呢？
	 */
	GLfloat bg[3];
	while (!glfwWindowShouldClose(window)) {
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
		glUseProgram(program);
		glBindVertexArray(vao);
			glEnableVertexAttribArray(0);
			
			bg[0] = 1.0f; bg[1] = 0.0f; bg[2] = 0.0f;
			glUniform3fv(bglocation, 1, bg);
			glBindBuffer(GL_ARRAY_BUFFER, leftup_bo);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); // 在绘制时才定义数组属性数据
			glDrawArrays(GL_TRIANGLES, 0, 3);
			
			bg[0] = 0.0f; bg[1] = 0.0f; bg[2] = 1.0f;
			glUniform3fv(bglocation, 1, bg);
			glBindBuffer(GL_ARRAY_BUFFER, leftdown_bo);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
			glDrawArrays(GL_TRIANGLES, 0, 3);

			bg[0] = 1.0f; bg[1] = 0.0f; bg[2] = 0.0f;
			glUniform3fv(bglocation, 1, bg);
			glBindBuffer(GL_ARRAY_BUFFER, rightup_bo);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
			glDrawArrays(GL_TRIANGLES, 0, 3);

			bg[0] = 0.0f; bg[1] = 0.0f; bg[2] = 1.0f;
			glUniform3fv(bglocation, 1, bg);
			glBindBuffer(GL_ARRAY_BUFFER, rightdown_bo);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
			glDrawArrays(GL_TRIANGLES, 0, 3);

			bg[0] = 0.0f; bg[1] = 0.5f; bg[2] = 0.0f;
			glUniform3fv(bglocation, 1, bg);
			glBindBuffer(GL_ARRAY_BUFFER, rect_bo);
			glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);			
		glBindVertexArray(0);
		glUseProgram(0);
		
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glDeleteProgram(program);
	glDeleteVertexArrays(1, &vao);
	glDeleteBuffers(1, &leftup_bo);
	glDeleteBuffers(1, &leftdown_bo);
	glDeleteBuffers(1, &rightup_bo);
	glDeleteBuffers(1, &rightdown_bo);
	glDeleteBuffers(1, &rect_bo);

	glfwTerminate();
	return 0;
}
